﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework.Input;

namespace GameUtils.Input
{
    public static class EventInput
    {
        #region Constants
        private const int GWL_WNDPROC = -4;

        #region Keyboard
        private const int WM_KEYDOWN = 0x0100;
        private const int WM_KEYUP = 0x0101;
        private const int WM_CHAR = 0x0102;
        #endregion

        #region Mouse
        private const int WM_LBUTTONDOWN = 0x0201;
        private const int WM_LBUTTONUP = 0x0202;
        private const int WM_MBUTTONDOWN = 0x0207;
        private const int WM_MBUTTONUP = 0x0208;
        private const int WM_RBUTTONDOWN = 0x0204;
        private const int WM_RBUTTONUP = 0x0205;
        private const int WM_MOUSEMOVE = 0x0200;
        private const int WM_MOUSEWHEEL = 0x020A;
        #endregion

        private const int WM_GETDLGCODE = 0x87;
        private const int DLGC_WANTALLKEYS = 4;
        private const int WM_INPUTLANGCHANGE = 0x51;
        private const int WM_IME_SETCONTEXT = 0x0281;
        //private const int WM_IME_COMPOSITION = 0x10f;
        #endregion

        #region Properties
        private static IntPtr mPrevWindowHandle;
        private static WindowProcedureDelegate mWindowProcedureDelegate;
        private static IntPtr mHIMC;

        private static bool mInitialized;
        #endregion

        #region Events
        #region Keyboard
        #region WMKeyDown
        public static event EventHandler<KeyEventArgs> WMKeyDown;
        private static void RaiseWMKeyDownEvent(Keys pKey)
        {
            var handler = WMKeyDown;
            if (handler != null)
            {
                handler(null, new KeyEventArgs(pKey));
            }
        }
        #endregion

        #region WMKeyUp
        public static event EventHandler<KeyEventArgs> WMKeyUp;
        private static void RaiseWMKeyUpEvent(Keys pKey)
        {
            var handler = WMKeyUp;
            if (handler != null)
            {
                handler(null, new KeyEventArgs(pKey));
            }
        }
        #endregion

        #region WMChar
        public static event EventHandler<CharacterEventArgs> WMChar;
        private static void RaiseWMCharEvent(char pCharacter, int pLParam)
        {
            var handler = WMChar;
            if (handler != null)
            {
                handler(null, new CharacterEventArgs(pCharacter, pLParam));
            }
        }
        #endregion
        #endregion

        #region Mouse
        #region WMLButtonDown
        public static event EventHandler<MouseEventArgs> WMLButtonDown;
        private static void RaiseWMLButtonDownEvent(int pWParam, int pLParam)
        {
            var handler = WMLButtonDown;
            if (handler != null)
            {
                handler(null, new MouseEventArgs(pWParam, pLParam));
            }
        }
        #endregion

        #region WMLButtonUp
        public static event EventHandler<MouseEventArgs> WMLButtonUp;
        private static void RaiseWMLButtonUpEvent(int pWParam, int pLParam)
        {
            var handler = WMLButtonUp;
            if (handler != null)
            {
                handler(null, new MouseEventArgs(pWParam, pLParam));
            }
        }
        #endregion

        #region WMMButtonDown
        public static event EventHandler<MouseEventArgs> WMMButtonDown;
        private static void RaiseWMMButtonDownEvent(int pWParam, int pLParam)
        {
            var handler = WMMButtonDown;
            if (handler != null)
            {
                handler(null, new MouseEventArgs(pWParam, pLParam));
            }
        }
        #endregion

        #region WMMButtonUp
        public static event EventHandler<MouseEventArgs> WMMButtonUp;
        private static void RaiseWMMButtonUpEvent(int pWParam, int pLParam)
        {
            var handler = WMMButtonUp;
            if (handler != null)
            {
                handler(null, new MouseEventArgs(pWParam, pLParam));
            }
        }
        #endregion

        #region WMRButtonDown
        public static event EventHandler<MouseEventArgs> WMRButtonDown;
        private static void RaiseWMRButtonDownEvent(int pWParam, int pLParam)
        {
            var handler = WMRButtonDown;
            if (handler != null)
            {
                handler(null, new MouseEventArgs(pWParam, pLParam));
            }
        }
        #endregion

        #region WMRButtonUp
        public static event EventHandler<MouseEventArgs> WMRButtonUp;
        private static void RaiseWMRButtonUpEvent(int pWParam, int pLParam)
        {
            var handler = WMRButtonUp;
            if (handler != null)
            {
                handler(null, new MouseEventArgs(pWParam, pLParam));
            }
        }
        #endregion

        #region WMMouseMove
        public static event EventHandler<MouseEventArgs> WMMouseMove;
        private static void RaiseWMMouseMoveEvent(int pWParam, int pLParam)
        {
            var handler = WMMouseMove;
            if (handler != null)
            {
                handler(null, new MouseEventArgs(pWParam, pLParam));
            }
        }
        #endregion

        #region WMMouseWheel
        public static event EventHandler<MouseEventArgs> WMMouseWheel;
        private static void RaiseWMMouseWheelEvent(int pWParam, int pLParam)
        {
            var handler = WMMouseWheel;
            if (handler != null)
            {
                handler(null, new MouseEventArgs(pWParam, pLParam));
            }
        }
        #endregion
        #endregion
        #endregion

        #region Constructors
        static EventInput()
        {
            mInitialized = false;
        }
        #endregion

        #region Methods
        #region Imported Methods
        [DllImport("Imm32.dll")]
        static extern IntPtr ImmGetContext(IntPtr pHWnd);
        [DllImport("Imm32.dll")]
        static extern IntPtr ImmAssociateContext(IntPtr pHWnd, IntPtr pHIMC);
        [DllImport("user32.dll")]
        static extern IntPtr CallWindowProc(IntPtr pLpPrevWndFunc, IntPtr pHWnd, uint pMsg, IntPtr pWParam, IntPtr pLParam);
        [DllImport("user32.dll")]
        static extern int SetWindowLong(IntPtr pHWnd, int pNIndex, int pDwNewLong);
        #endregion

        public static void Initialize(GameWindow pWindow)
        {
            if (mInitialized)
            {
                return;
            }

            mWindowProcedureDelegate = new WindowProcedureDelegate(WindowProcedureImpl);
            mPrevWindowHandle = (IntPtr)SetWindowLong(
                pWindow.Handle,
                GWL_WNDPROC,
                (int)Marshal.GetFunctionPointerForDelegate(mWindowProcedureDelegate));
            mHIMC = ImmGetContext(pWindow.Handle);

            mInitialized = true;
        }

        private static IntPtr WindowProcedureImpl(IntPtr pHWnd, uint pMsg, IntPtr pWParam, IntPtr pLParam)
        {
            IntPtr returnCode = CallWindowProc(mPrevWindowHandle, pHWnd, pMsg, pWParam, pLParam);

            switch (pMsg)
            {
                #region Keyboard
                case WM_KEYDOWN:
                    RaiseWMKeyDownEvent((Keys)pWParam);
                    break;

                case WM_KEYUP:
                    RaiseWMKeyUpEvent((Keys)pWParam);
                    break;

                case WM_CHAR:
                    RaiseWMCharEvent((char)pWParam, pLParam.ToInt32());
                    break;
                #endregion

                #region Mouse
                case WM_LBUTTONDOWN:
                    RaiseWMLButtonDownEvent(pWParam.ToInt32(), pLParam.ToInt32());
                    break;

                case WM_LBUTTONUP:
                    RaiseWMLButtonUpEvent(pWParam.ToInt32(), pLParam.ToInt32());
                    break;

                case WM_MBUTTONDOWN:
                    RaiseWMMButtonDownEvent(pWParam.ToInt32(), pLParam.ToInt32());
                    break;

                case WM_MBUTTONUP:
                    RaiseWMMButtonUpEvent(pWParam.ToInt32(), pLParam.ToInt32());
                    break;

                case WM_RBUTTONDOWN:
                    RaiseWMRButtonDownEvent(pWParam.ToInt32(), pLParam.ToInt32());
                    break;

                case WM_RBUTTONUP:
                    RaiseWMRButtonUpEvent(pWParam.ToInt32(), pLParam.ToInt32());
                    break;

                case WM_MOUSEMOVE:
                    RaiseWMMouseMoveEvent(pWParam.ToInt32(), pLParam.ToInt32());
                    break;

                case WM_MOUSEWHEEL:
                    RaiseWMMouseWheelEvent(pWParam.ToInt32(), pLParam.ToInt32());
                    break;
                #endregion

                case WM_GETDLGCODE:
                    returnCode = (IntPtr)(returnCode.ToInt32() | DLGC_WANTALLKEYS);
                    break;

                case WM_IME_SETCONTEXT:
                    if (pWParam.ToInt32() == 1)
                    {
                        ImmAssociateContext(pHWnd, mHIMC);
                    }
                    break;

                case WM_INPUTLANGCHANGE:
                    ImmAssociateContext(pHWnd, mHIMC);
                    returnCode = (IntPtr)1;
                    break;
            }

            return returnCode;
        }
        #endregion

        #region Inner Classes
        private delegate IntPtr WindowProcedureDelegate(IntPtr pHWnd, uint pMsg, IntPtr pWParam, IntPtr pLParam);
        #endregion
    }
}